/*
 * Copyright (C) 2012 Tomas Shestakov. <http://code.google.com/p/jmmo/>
 */

package jmmo.engine.multifacetedactor

import jmmo.util.applyIfDefined
import akka.actor.{InvalidMessageException, Actor}
import collection.immutable.Queue

/**
 *
 * @author Tomas Shestakov
 */
trait MultifacetedActor extends Actor {

  def role(id: Any): Option[ActorRole] = availableRoles.get(id)

  def roles: Iterable[ActorRole] = availableRoles.values

  /**
   * <p>Stores internal communications messages, which manager communicates with the roles.
   * <p>Хранит внутренние сообщения, которыми менеджер обменивается с ролями.
   */
  protected var internalMessages = Queue.empty[Any]

  /**
   * Sends a message only into actor to his roles.
   * @param msg a message to send.
   */
  def sendInside(msg: Any) {
    internalMessages = internalMessages.enqueue(msg)
  }

  /**
   * Sends an event to [[akka.event.EventStream]] of actor system and into actor to his roles.
   * @param event an event to send.
   */
  def sendEvent(event: AnyRef) {
    sendInside(event)
    context.system.eventStream.publish(event)
  }

  def addRole(role: ActorRole) {
    if (availableRoles.contains(role.id) || revokedRoles.contains(role.id)) {
      throw new IllegalArgumentException(role + " is already contained in " + this)
    }
    revokedRoles += (role.id -> role)
    role.context = this.context
    role.actor = this.asInstanceOf[role.OwnerActor]
    if (this.active) {
      //если актер уже в работе инициализируем роль
      //if actor is in action then initialize role at once
      role.preStart()
      //отсылаем новой роли сообщения о добавлении всех доступных ролей
      //sending to new role messages about adding all available roles
      availableRoles.values.foreach(avRole => applyIfDefined(role.receive, RoleAvailable(avRole)))
    }
  }

  def removeRole(role: ActorRole) {
    val revokedRole = availableRoles.get(role.id) match {
      case Some(r) => {
        availableRoles -= r.id
        if (this.active) {
          refreshReceive()
          val revoked = RoleRevoked(r)
          applyIfDefined(rolesReceive(revokedRoles), revoked)
          applyIfDefined(availableRolesReceive, revoked)
        }
        r
      }
      case None => {
        revokedRoles.get(role.id) match {
          case Some(r) => {
            revokedRoles -= r.id
            r
          }
          case None => throw new IllegalArgumentException("Role with id=" + role.id + " is not contained in " + this)
        }
      }
    }
    revokedRole.context = null
    revokedRole.actor = null.asInstanceOf[revokedRole.OwnerActor]
    //если актер еще не стартовал то у роли еще не вызывался preStart, а значит и postStop не нужен
    //if the actor has not started then the role has not been called preStart, and therefore do not need postStop
    if (this.active) revokedRole.postStop()
  }

  protected var availableRoles = Map.empty[Any, ActorRole]

  protected var revokedRoles = Map.empty[Any, ActorRole]

  /**
   * True if actor is in action (started and not stopped, not terminated and so on)
   */
  protected var active = false

  protected var availableRolesReceive: Actor.Receive = _

  protected var actorReceive: Actor.Receive = _

  private def rolesReceive(roles: Map[Any, ActorRole]) = new Actor.Receive {
    def isDefinedAt(msg: Any) = roles.values.exists(_.receive.isDefinedAt(msg))
    def apply(msg: Any) {
      roles.values.withFilter(_.receive.isDefinedAt(msg)).foreach (_.receive.apply(msg))
    }
  }

  protected def refreshReceive() {
    availableRolesReceive = rolesReceive(availableRoles)
    actorReceive = receive orElse availableRolesReceive
    context.become(new Actor.Receive {
      def isDefinedAt(msg: Any) = actorReceive.isDefinedAt(msg)
      def apply(msg: Any) {
        actorReceive.apply(msg)
        handleInternalMessages()
      }
    }, true)
  }

  protected def handleInternalMessages() {
    while (internalMessages.nonEmpty) {
      //копируем очередь внутренних сообщений, поскольку сообщения могут добавляться при обработке
      val messages = internalMessages
      internalMessages = Queue.empty
      messages.foreach(applyIfDefined(actorReceive, _))
    }
  }

  override def receive = /*jmmo.util.emptyPartial*/ {

      case msg @ RoleAvailable(role) => {
        val gameRole = revokedRoles.get(role.id) match {
          case Some(r) => r
          case None => throw new InvalidRoleException(role + " is not in list of revoked roles of " + this)
        }
        revokedRoles -= role.id
        availableRoles += (role.id -> gameRole)
        //message about the availability of the new role will be received by all except the new role
        //сообщение о доступности новой роли получат все кроме самой новой роли
        val prevRolesReceive = availableRolesReceive
        refreshReceive()
        applyIfDefined(prevRolesReceive, msg)
        applyIfDefined(rolesReceive(revokedRoles), msg)
      }

      case msg @ RoleRevoked(role) => {
        val gameRole = availableRoles.get(role.id) match {
          case Some(r) => r
          case None => throw new InvalidRoleException(role + " is not in list of available roles of " + this)
        }
        availableRoles -= role.id
        revokedRoles += (role.id -> gameRole)
        val prevRolesReceive = rolesReceive(revokedRoles)
        refreshReceive()
        applyIfDefined(prevRolesReceive, msg)
        applyIfDefined(availableRolesReceive, msg)
      }
    }

  override def preStart() {
    active = true
    //on start actor there are not available roles
    revokedRoles.values.foreach(_.preStart())
    refreshReceive()
    handleInternalMessages()
    super.preStart()
  }

  override def postStop() {
    active = false
    (availableRoles.values ++ revokedRoles.values).foreach(_.postStop())
    super.postStop()
  }

  override def preRestart(reason: Throwable, message: Option[Any]) {
    (availableRoles.values ++ revokedRoles.values).foreach(_.preRestart(reason, message))
    super.preRestart(reason, message)
  }

  override def postRestart(reason: Throwable) {
    //on restart actor there are not available roles
    revokedRoles.values.foreach(_.postRestart(reason))
    super.postRestart(reason)
  }
}



